package com.googlecode.scup.parser.automat;

import com.googlecode.scup.grammar.Grammar;
import com.googlecode.scup.grammar.ExtensionSimpleGrammar;
import com.googlecode.scup.grammar.grammarspart.*;
import com.googlecode.scup.parser.actionstates.*;

import java.util.*;

public class Automat {
    private ExtensionSimpleGrammar grammar;
    private List<AutomatState> states = null;
    private List<Map<Symbol, Integer>> table;

    public Automat(Grammar grammar) {
        this.grammar = new ExtensionSimpleGrammar(grammar);
        states = new ArrayList<AutomatState>();
        table = new ArrayList<Map<Symbol, Integer>>();
        fill(this.grammar.getStartRule());
    }

    private void fill(Rule startRule) {
        AutomatState state = new AutomatState(AutomatRule.createAutomatRules(startRule), grammar);
        Stack<Symbol> transitions = new Stack<Symbol>();
        states.add(state);
        table.add(new HashMap<Symbol, Integer>());
        for(int i = 0; i < states.size(); ++i) {
            transitions.addAll(states.get(i).getAllSymbols());
            while (! transitions.isEmpty()) {
                state = new AutomatState(states.get(i).getTransitionRule(transitions.peek()), grammar);
                if (! states.contains(state)) {
                    table.get(i).put(transitions.pop(), states.size());
                    states.add(state);
                    table.add(new HashMap<Symbol, Integer>());
                } else {
                    table.get(i).put(transitions.pop(), states.indexOf(state));
                }
            }
        }
    }

    public List<Symbol> First(Symbol symbol) {
        List<Symbol> result = new ArrayList<Symbol>();
        if (symbol.isTerminal()) {
            result.add(symbol);
        } else {
        	List<Rule> rules = new ArrayList<Rule>();
        	rules.add(grammar.getRule(symbol));
        	for (int i = 0; i < rules.size(); ++i) {
        		for(Production production : rules.get(i).getProductions()) {
        			if (production.getSymbol(0).isTerminal()){
        				result.add(production.getSymbol(0));
        			} else {
        				if (! rules.contains(grammar.getRule(production.getSymbol(0)))){
        					rules.add(grammar.getRule(production.getSymbol(0)));
        				}
        			}
        		}
        	}
        }
        return result;
    }

    public Set<Symbol> Follow (Symbol symbol) {
    	Set<Symbol> followset = new HashSet<Symbol>();

    	if (symbol.equals(grammar.getStartRule().getBase())) {
    		followset.add(new Symbol("$"));
    	}

    	for (Rule rule : grammar.getRules()) {
    		for (Production production : rule.getProductions()) {
    			int after = production.getProduction().indexOf(symbol);
    			if (after != -1) {
    				Symbol nextSymbol = production.getSymbol(after + 1);
    				if (nextSymbol != null) {
    					if (nextSymbol.isTerminal()) {
    						followset.add(nextSymbol);
    						//followset.addAll(First(nextSymbol));
    					} else {
    						List<Symbol> s = First(nextSymbol);
    						if (s.size() != 0) {
    							followset.addAll(s);
    						} else {
    							if (!rule.getBase().equalsByName(symbol)) { // FIXME:
    								followset.addAll(Follow(rule.getBase()));
    							}
    						}
    					}
    				} else {
						if (!rule.getBase().equalsByName(symbol)) { // FIXME:
							followset.addAll(Follow(rule.getBase()));
						}
    				}
    			}
    		}
    	}

    	return followset;
    }

    public List<Map<Symbol, Integer>> getTable() {
        return table;
    }

    public List<Map<Symbol, ActionState>> getShift(List<Map<Symbol, ActionState>> result) {
        for (int i = 0; i < states.size(); ++i) {
            if (result.size() <= i) result.add(new HashMap<Symbol, ActionState>());
            for(Symbol symbol : states.get(i).getTerminals()) {
                result.get(i).put(symbol, new ShiftAction(this.table.get(i).get(symbol)));
            }
        }
        return result;
    }

    public List<Map<Symbol, ActionState>> getReduce(List<Map<Symbol, ActionState>> result) {
        for (int i = 0; i < states.size(); ++i) {
            if (result.size() <= i) result.add(new HashMap<Symbol, ActionState>());
            for(AutomatRule rule : states.get(i).getEndRules()) {
                for(Symbol symbol : Follow(rule.getBase())) {
                    result.get(i).put(symbol, new ReduceAction(rule));
                }
            }
        }
        return result;
    }

    public List<Map<Symbol, ActionState>> getGoto(List<Map<Symbol, ActionState>> result) {
        for (int i = 0; i < states.size(); ++i) {
            if (result.size() <= i) result.add(new HashMap<Symbol, ActionState>());
            for(Symbol symbol : states.get(i).getNonTerminals()) {
                result.get(i).put(symbol, new GotoAction(this.table.get(i).get(symbol)));
            }
        }
        return result;
    }

    public List<Map<Symbol, ActionState>> getAccept(List<Map<Symbol, ActionState>> result) {
        Symbol toAcceptState = grammar.getStartRule().getProductions().get(0).getSymbol(0);
        int indexAcceptState = table.get(0).get(toAcceptState);
        result.get(indexAcceptState).put(new Symbol("$", true), new AcceptAction());
        return result;
    }

    public List<Map<Symbol, ActionState>> getErrorAction(List<Map<Symbol, ActionState>> result) {
        //mb catch Exception will be easier ?
        return result;
    }

    public String toString() {
        //TODO: make normally toString() if it really need
        StringBuilder result = new StringBuilder();
        result.append("States count: ");
        result.append(states.size());
        for (AutomatState state : states) {
            result.append('\n');
            result.append(state);
        }
        return result.toString();
    }

    public String toStringTable() {
        StringBuilder result = new StringBuilder();
        for (Map<Symbol, Integer> map : table) {
            result.append('\n');
            result.append(map);
        }
        return result.toString();
    }
}
